package orbito.scene;

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.geom.AffineTransform;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;

import orbito.entity.Planetoid;
import orbito.entity.PlanetoidSystem;
import orbito.graphics.ViewFocus;
import orbito.graphics.MessageQueue;
import orbito.graphics.View;
import orbito.graphics.Viewport;
import orbito.physics.PhysicsUtils;
import orbito.physics.motion.DefaultForceManager;
import orbito.physics.motion.GravitationalForce;
import orbito.physics.motion.Integrator;
import orbito.physics.motion.RungeKuttaIntegrator;

public abstract class PlanetoidSystemTemplateScene implements SceneGraph {
	
	Random rand;
	MessageQueue messages;
	
	DefaultForceManager forceManager;
	Integrator integrator;
	
	View view;
	
	PlanetoidSystem system;
	Planetoid[] planetoids;
	
	List<ViewFocus> viewFocusItems;
	
	public abstract String getSceneName();
	protected abstract PlanetoidSystem createSystem(Random rand);
	
	public void init(Random rand, MessageQueue messages) {
		this.rand = rand;
		this.messages = messages;
		
		forceManager = new DefaultForceManager();
		integrator = new RungeKuttaIntegrator(forceManager);
		viewFocusItems = new LinkedList<ViewFocus>();
		
		view = new View();
		
		//PhysicsSettings.gravCoeff = 1;
		
		system = createSystem(rand);
		Planetoid centroid = system.getCentroid();
		
		view.setFocus(centroid);
		view.setScale(100 / centroid.getRadius());
		
		Collection<Planetoid> planetoidsCollection = system.assembleNodes();
		planetoids = new Planetoid[planetoidsCollection.size()];
		planetoidsCollection.toArray(planetoids);
		
		// Add to view focus items.
		viewFocusItems.add(system);
		
		// Add planetoids to integrator.
		for (Planetoid p : planetoids) {
			integrator.addActor(p);
		}
		
		// Create gravitational forces between all pairs of planetoids.
		for (int i = 0; i < planetoids.length - 1; i++) {
			Planetoid p1 = planetoids[i];
			for (int j = i + 1; j < planetoids.length; j++) {
				Planetoid p2 = planetoids[j];
				forceManager.addForce(new GravitationalForce(p1, p2));
			}
		}
	}
	
	public void step(double t, double dt) {
		// Step the system.
		system.step(t, dt);
		
		// Calculate the motions.
		integrator.integrate(t, dt);
	}
	
	public void render(Graphics2D g, double t, double dt) {
		// Render the system.
		system.render(g, t, dt);
		
		// Check orbits.
		checkOrbits();
	}

	public void renderInfo(Graphics2D g, Viewport v, AffineTransform tx, double t, double dt) {
		// Render the system info.
		system.renderInfo(g, v, tx, t, dt);
	}
	
	public void handleInput(double dt) {
		// Nothing to do here.
	}

	public List<View> getViews() {
		List<View> views = new LinkedList<View>();
		views.add(view);
		return views;
	}

	public View getCurrentView() {
		return view;
	}

	public void setCurrentView(View view) {
		this.view = view;
	}
	
	public List<ViewFocus> getViewFocusItems() {
		return viewFocusItems;
	}
	
	// -------------------------------------------
	// Helper Methods.
	
	private void checkOrbits() {
		for (Planetoid p : planetoids) {
			// Determine if the planetoid has escaped the orbit of its parent.
			if (p.getOrbitalParent() != null && !PhysicsUtils.isInOrbit(p, p.getOrbitalParent())) {
				String message = "" + p.getName() + " no longer orbits " + p.getOrbitalParent().getName() + "";
				Planetoid centroid = findOrbitalParent(p, planetoids);
				
				if (centroid != null) {
					message += ", it now orbits " + centroid.getName() + "!";
				} else {
					message += ", and is leaving the solar system!";
				}
				
				p.setOrbitalParent(centroid);
				messages.addMessage(message, Color.RED);
			}
		}
	}
	
	private Planetoid findOrbitalParent(Planetoid child, Planetoid[] planetoids) {
		double minDist = Double.POSITIVE_INFINITY;
		Planetoid best = null;
		
		for (Planetoid parent : planetoids) {
			if (parent != child && PhysicsUtils.isInOrbit(child, parent)) {
				double dist = parent.getPosition().sub(child.getPosition()).length();
				if (dist < minDist) {
					minDist = dist;
					best = parent;
				}
			}
		}
		
		return best;
	}
}
